#region Copyright (c) 2007-2008, Chris Klochek

/********************************************************************************************************************
'
' Copyright (c) 2007-2008, Chris Klochek
' All rights reserved.
'
' Redistribution and use in source and binary forms, with or without modification, are permitted provided
' that the following conditions are met:
'
' * Redistributions of source code must retain the above copyright notice, this list of conditions and the
' 	following disclaimer.
'
' * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
' 	the following disclaimer in the documentation and/or other materials provided with the distribution.
'
' * Neither the name of the author nor the names of its contributors may be used to endorse or
' 	promote products derived from this software without specific prior written permission.
'
' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
' WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
' PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
' ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
' LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
' INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
' OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
' IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'
'*******************************************************************************************************************/

#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace SuedeTest.Core {
   public class FormRepository {
      private static Win32.CBTCallback _callback = null;
      private static Win32.CBTCallback _msgCallback = null;

      private static List<KeyValuePair<int, IntPtr>> _hookHandles;

      private static List<Form> _forms = new List<Form>();
      private const int CbtHookType = 5;
      private const int MsgFilterHookType = -1;

      private const int HCBT_ACTIVATE = 5;
      private const int HCBT_DESTROY = 4;
      private const int HCBT_CREATE = 3;

      public static List<Form> Forms {
         get {
            lock (_forms) {
               return _forms;
            }
         }
      }

      public delegate void FormAddedCallback(Form form);
      public static event FormAddedCallback OnFormAdded;

      public delegate void DialogButtonClickedCallback(string dialogName, string buttonName);
      public static event DialogButtonClickedCallback OnDialogButtonClicked;

      /// <summary>
      /// Starts the form listener on this thread (does not unregister the listener on other threads)
      /// </summary>
      ///
      public static void InitFormRepositoryOnThread() {
         if (_callback == null) {
            _callback = WindowCreationCallback;
            _msgCallback = MsgProc;
            _hookHandles = new List<KeyValuePair<int, IntPtr>>();
         }

         _hookHandles.Add(new KeyValuePair<int, IntPtr>(Win32.GetCurrentThreadId(), Win32.SetWindowsHookEx(MsgFilterHookType, _msgCallback, IntPtr.Zero, Win32.GetCurrentThreadId())));
         _hookHandles.Add(new KeyValuePair<int, IntPtr>(Win32.GetCurrentThreadId(), Win32.SetWindowsHookEx(CbtHookType, _callback, IntPtr.Zero, Win32.GetCurrentThreadId())));
      }

      /// <summary>
      /// remove hooks
      /// </summary>
      public static void CloseRepositoryOnThread() {
         int currentThreadID = Win32.GetCurrentThreadId();

         for (int x = 0; x < _hookHandles.Count; x++) {
            KeyValuePair<int, IntPtr> hookPair = _hookHandles[x];

            if (hookPair.Key == currentThreadID && hookPair.Value != IntPtr.Zero) {
               Win32.UnhookWindowsHookEx(hookPair.Value);
               _hookHandles.Remove(hookPair);
               x--;
            }
         }
      }

      /// <summary>
      ///Callback to Hook Message window
      /// </summary>
      /// <param name="code"></param>
      /// <param name="wParam"></param>
      /// <param name="lParam"></param>
      /// <returns></returns>
      private static IntPtr MsgProc(int code, IntPtr wParam, IntPtr lParam) {
         Win32.Win32Msg msg;

         if (code >= 0) {

            msg = (Win32.Win32Msg)Marshal.PtrToStructure(lParam, typeof(Win32.Win32Msg));

            if (msg.message == Win32.WM_LMOUSEBUTTONDOWN) {
               if (!WindowHandle.IsDialog(msg.hWnd)) {
                  string controlName = WindowHandle.GetCaption(msg.hWnd);

                  //contextmenu's are also dialog/popups, so they can trigger this code, too.
                  //if we check for empty controlNames, though, we appear to be safe
                  if (!string.IsNullOrEmpty(controlName)) {
                     IntPtr parentWnd = Win32.GetParent(msg.hWnd);

                     string messageBoxName = WindowHandle.GetCaption(parentWnd);

                     if (OnDialogButtonClicked != null) {
                        OnDialogButtonClicked(messageBoxName, controlName);
                     }
                  }
               }
            }
         }

         return Win32.CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
      }

      /// <summary>
      /// Window creation callback. Actually checks for whether window is created and activated.
      /// If window is created or activated then form is added to repository
      /// on HCBT_DESTROY form is removed from the repository
      /// </summary>
      /// <param name="code"></param>
      /// <param name="wParam"></param>
      /// <param name="lParam"></param>
      /// <returns></returns>
      private static IntPtr WindowCreationCallback(int code, IntPtr wParam, IntPtr lParam) {
         lock (_forms) {
            if (code == HCBT_CREATE || code == HCBT_ACTIVATE) {  //window creation/activation
               Control c = Control.FromHandle(wParam);

               if (c != null) {
                  Form f = c as Form;

                  if (f != null) {
                     lock (_forms) {
                        if (!_forms.Contains(f)) {
                           _forms.Add(f);

                           //inform all listeners
                           if (OnFormAdded != null) {
                              OnFormAdded(f);
                           }

                           if (f.Modal) {
                              EventHelper.ReleaseWaitHandle();
                           }
                        }
                     }
                  }
               }
            } else if (code == HCBT_DESTROY) {
               Form f = Form.FromHandle(wParam) as Form;

               RemoveForm(f);
            }
         }

         return Win32.CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
      }
      

      public static void RemoveForm(Form f) {
         if (f != null) {
            lock (_forms) {
               _forms.Remove(f);
            }
         }
      }
   }
}